#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "PCB.h"
#include "r2.h"
#include "MPX_SUPT.H"
#include "R3.h"

pcb *ready_head = NULL;
pcb *ready_tail = NULL;
pcb *blocked_head = NULL;
pcb *blocked_tail = NULL;
pcb *suspended_ready_head = NULL;
pcb *suspended_ready_tail = NULL;
pcb *suspended_blocked_head = NULL;
pcb *suspended_blocked_tail = NULL;

//procedures for use on PCBs

/*Allocates a PCB using sys_alloc_mem, marks it as in use, 
 *and returns pcbPtr type that points to the PCB.
 *
 *Parameters: None
 *Returns: pcbPtr, a type that is equivalent to pcb *.  returns null if allocate fails
 */
pcb *allocate_PCB() {
	//tip - use sizeof with sys_alloc_mem to get correct number of bytes
	pcb *newpcb;
	newpcb =  (pcb*) sys_alloc_mem( sizeof(pcb));
		if(newpcb == NULL){
			printf("There was an error creating the PCB\n");
		}
	newpcb->stack_base = (unsigned char *)sys_alloc_mem(sizeof(unsigned char)*1024);
	newpcb->stack_top = newpcb->stack_base + 1025 - sizeof(context);
	return newpcb;
	
}

/*Initializes PCB by calling allocate-PCB and then setting values based on
 *defaults and user input given in parameters
 *
 *Parameters: name, a char array holding the desired name
 *            type, an int giving the class (application or system)
 *            priority, an int giving process' desired priority
 *Returns: pcb *.  returns null if allocate fails or if invalid parameter
 */
pcb *setup_PCB(char name0[], int type0, int priority0) {
	//note: parameters and return values based on TA's lab slides for R2
	//(setup calls allocate), not R2 manual (allocate called then setup called)

	//check priority x for -128 <= x <= 127
	//check name for length less than PROC_NAME_LEN (or just use only that many chars)
	//also check check that name (or first PROC_NAME_LEN number of chars) is unique
	//check tyoe for either PROC_CLASS_APP or PROC_CLASS_SYS
	//use default values for remaining fields during initialization
	pcb *anew_pcb = NULL;
	if(check_name(name0) == 0){
		anew_pcb = allocate_PCB();
		strcpy(anew_pcb->name, name0);
		anew_pcb->type = type0;
		anew_pcb->priority = priority0;
		anew_pcb->state = PROC_STATE_READY;
		anew_pcb->suspended = PROC_NOT_SUSP;
		anew_pcb->next = NULL;
		anew_pcb->prev = NULL;
		anew_pcb->mem_load_address = NULL;
		anew_pcb->mem_exec_address = NULL;
	}
	else{
		printf("The name is not unique\n");
	}
	return anew_pcb;
}

//Parameters: 	name, char[] with name of PCB to find
//Returns: pcb* pointing to PCB if found, NULL if it does not exist
//Functions called:	traverse_find (searches queue pointed by given head for given name)
//Searches all queues for a PCB with the given name until it finds it or reaches the end. Returns accordingly
pcb *find_pcb(char name[]){
	pcb *curr = NULL;
	//ready
	curr = traverse_find(ready_head, name);
	if (curr != NULL)
		return curr;
	//blocked
	curr = traverse_find(blocked_head, name);
	if (curr != NULL)
		return curr;
	//suspended-ready
	curr = traverse_find(suspended_ready_head, name);
	if (curr != NULL)
		return curr;
	//suspended-blocked
	curr = traverse_find(suspended_blocked_head, name);
	if (curr != NULL)
		return curr;
	//check running? (just in case the new process has same name as running process, if possible?)
	if (curr == NULL){
		pcb *temp;
		temp = get_to_run();
		if(name == temp->name)
			return temp;
	}
	return NULL;
}

//helper function for find that compares the name of each element of queue
//returns null if name not in queue
pcb *traverse_find(pcb *head, char name[]) {
	pcb *curr = head;
	
	while (curr != NULL){
	
		if (strcmp(curr->name, name) == 0){
				return curr;
		}
			curr = curr->next;
	}
	return NULL;
}

/*
*	This function is used to insert a PCB into its queue
*	Uses the traverse() function
*	Parameters: this_pcb: a pointer to a PCB
*				state and suspended: two ints that specify which queue we are putting it in
*	Returns: int codes to confirm it was done properly, or an error code
*/
int insert_pcb(pcb *this_pcb, int state, int suspended){
	
	pcb *lower_node = NULL;
	pcb *higher_node = NULL;

	if(state == PROC_STATE_READY){
		//use ready_head (or suspended, if needed), insert based on priority
		if(suspended == PROC_NOT_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(ready_head == NULL){
				ready_head = this_pcb;
				ready_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > ready_head->priority) {
					ready_head->prev = this_pcb;
					this_pcb->next = ready_head;
					ready_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= ready_tail->priority) {
					ready_tail->next = this_pcb;
					this_pcb->prev = ready_tail;
					ready_tail = this_pcb;
				}
				else{
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(ready_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
				}
			}
		}
		else if(suspended == PROC_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(suspended_ready_head == NULL){
				suspended_ready_head = this_pcb;
				suspended_ready_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > suspended_ready_head->priority) {
					suspended_ready_head->prev = this_pcb;
					this_pcb->next = suspended_ready_head;
					suspended_ready_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= suspended_ready_tail->priority) {
					suspended_ready_tail->next = this_pcb;
					this_pcb->prev = suspended_ready_tail;
					suspended_ready_tail = this_pcb;
				}
				else{
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(suspended_ready_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
				}
			}
		}
		else{//it is not a valid suspended value
			printf("That is not a valid value for suspended for the insert-pcb function\n");
			return 1;
		}
	}
	else if(state == PROC_STATE_BLOCKED){
		if(suspended == PROC_NOT_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(blocked_head == NULL){
				blocked_head = this_pcb;
				blocked_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > blocked_head->priority) {
					blocked_head->prev = this_pcb;
					this_pcb->next = blocked_head;
					blocked_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= blocked_tail->priority) {
					blocked_tail->next = this_pcb;
					this_pcb->prev = blocked_tail;
					blocked_tail = this_pcb;
				}
				else{
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(blocked_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
				}
			}	
		}
		else if(suspended == PROC_SUSP){
			this_pcb->state = state;
			this_pcb->suspended = suspended;
			if(suspended_blocked_head == NULL){
				suspended_blocked_head = this_pcb;
				suspended_blocked_tail = this_pcb;
				this_pcb->prev = NULL;
				this_pcb->next = NULL;
			}
			else{
				//check if greater than head, insert at beginning if so
				if (this_pcb->priority > suspended_blocked_head->priority) {
					suspended_blocked_head->prev = this_pcb;
					this_pcb->next = suspended_blocked_head;
					suspended_blocked_head = this_pcb;
				}
				//check if less than or equal to tail, insert at end if so
				else if (this_pcb->priority <= suspended_blocked_tail->priority) {
					suspended_blocked_tail->next = this_pcb;
					this_pcb->prev = suspended_blocked_tail;
					suspended_blocked_tail = this_pcb;
				}
				else{
				//traverse to find node with lower priority (will be at least 2 nodes in queue)
				lower_node = traverse(suspended_blocked_head, this_pcb->priority);
				//update pointers based on returned node
				higher_node = lower_node->prev;
				lower_node->prev = this_pcb;
				this_pcb->next = lower_node;
				higher_node->next = this_pcb;
				this_pcb->prev = higher_node;
				}
			}
		}
		else{//it is not a valid suspended value
			printf("That is not a valid value for suspended for the insert-pcb function\n");
			return 1;
		}
	}
	else{//it is not a valid state
		printf("That is not a valid state for the insert-pcb function\n");
		return 1;
	}
	return 0;
}

/*
*	This function goes through a given queue and looks at each PCB for one with a lower priority
*	NOTE: does not check if head is null - error will result if so!
*	Parameters: *head: a pointer to the head of the necessary queue
*				priority: an int that is the priority from the PCB we are placing 
*	Returns: a pointer to the first element with a lower priority, the one that we will need to insert in front of	
*/
pcb *traverse(pcb *head, int priority){
	pcb *curr = head;
	while ((curr->next != NULL) && (curr->priority >= priority)) {
		curr = curr->next;
	}
	return curr;
}

/*
*	This function releases all memory held by a PCB, it should be removed from a queue with remove_pcb before it is freed
*	Parameters: *this_pcb: a pointer to the PCB to be removed
*	Returns: integer success or error codes
*	
*/
int free_pcb(pcb *this_pcb){
	int free_mem_error = -1;
	free_mem_error = sys_free_mem(this_pcb->mem_load_address);
	if(free_mem_error != 0){
		printf("There was a problem freeing the load address\n");
	}
	free_mem_error = -1;
	free_mem_error = sys_free_mem(this_pcb->stack_base);
	if(free_mem_error != 0){
		printf("There was a problem freeing the stack\n");
	}
	free_mem_error = -1;
	free_mem_error = sys_free_mem(this_pcb);
	if(free_mem_error != 0){
		printf("There was a problem freeing the PCB\n");
	}
	return free_mem_error;
}

/*
*	This function removed a PCB from it queue and connects the queue around it
*	Paramerers: *this_pcb: a pointer to the PCB to be removed
*				*head: a pointer the the start of the queue the pcb is in
*	Returns: integer success or error codes
*/
int remove_pcb(pcb *this_pcb){
	pcb *before = this_pcb->prev;
	pcb *after = this_pcb->next;
	
	if(this_pcb->state == PROC_STATE_READY){
		if(this_pcb->suspended == PROC_NOT_SUSP){
			if(this_pcb->next == NULL && this_pcb->prev == NULL){ //only pcb in queue
				ready_head = NULL;
				ready_tail = NULL;
			}
			else if(this_pcb->next == NULL){ //last pcb in queue
				ready_tail = before;
				before->next = NULL;//added this to correct bad removal of tail
			}
			else if(this_pcb->prev == NULL){ //first pcb in queue
				ready_head = after;
				after->prev = NULL;//added this to correct bad removal of head
			}
			else{ // somewhere in the middle
				before->next = this_pcb->next;
				after->prev = this_pcb->prev;
			}
		}
		else if(this_pcb->suspended == PROC_SUSP){
			if(this_pcb->next == NULL && this_pcb->prev == NULL){ //only pcb in queue
				suspended_ready_head = NULL;
				suspended_ready_tail = NULL;
			}
			else if(this_pcb->next == NULL){ //last pcb in queue
				suspended_ready_tail = before;
				before->next = NULL;//added this to correct bad removal of tail
			}
			else if(this_pcb->prev == NULL){ //first pcb in queue
				suspended_ready_head = after;
				after->prev = NULL;//added this to correct bad removal of head
			}
			else{ // somewhere in the middle
				before->next = this_pcb->next;
				after->prev = this_pcb->prev;
			}
		}
		else{
			printf("Error in remove PCB\n");
			return 1;
		}
	}
	else if(this_pcb->state == PROC_STATE_BLOCKED){
		if(this_pcb->suspended == PROC_NOT_SUSP){
			if(this_pcb->next == NULL && this_pcb->prev == NULL){ //only pcb in queue
				blocked_head = NULL;
				blocked_tail = NULL;
			}
			else if(this_pcb->next == NULL){ //last pcb in queue
				blocked_tail = before;
				before->next = NULL;//added this to correct bad removal of tail
			}
			else if(this_pcb->prev == NULL){ //first pcb in queue
				blocked_head = after;
				after->prev = NULL;//added this to correct bad removal of head
			}
			else{ // somewhere in the middle
				before->next = this_pcb->next;
				after->prev = this_pcb->prev;
			}
		}
		else if(this_pcb->suspended == PROC_SUSP){
			if(this_pcb->next == NULL && this_pcb->prev == NULL){ //only pcb in queue
				suspended_blocked_head = NULL;
				suspended_blocked_tail = NULL;
			}
			else if(this_pcb->next == NULL){ //last pcb in queue
				suspended_blocked_tail = before;
				before->next = NULL;//added this to correct bad removal of tail
			}
			else if(this_pcb->prev == NULL){ //first pcb in queue
				suspended_blocked_head = after;
				after->prev = NULL;//added this to correct bad removal of head
			}
			else{ // somewhere in the middle
				before->next = this_pcb->next;
				after->prev = this_pcb->prev;
			}
		}
		else{
			printf("Error in remove PCB\n");
			return 1;
		}
	}
	else if(this_pcb->state == PROC_STATE_RUNNING){
		//printf("done..running\n");
		return 1;
	}
	else{
		printf("Error in remove PCB\n");
		return 1;
	}
	
	this_pcb->prev = NULL; //release pointers within this pcb
	this_pcb->next = NULL;
	return 0;
}

/*
*	checks to see if a name is in any queue
*	Returns: 0 if not in any queue, 1 if in a queue
*	Uses: traverse find finction
*/
int check_name(char name[]){
	int in_one, in_two, in_three, in_four = 1;
	if(traverse_find(ready_head, name) == NULL){
		in_one = 0;
	}
	if(traverse_find(blocked_head, name) == NULL){
		in_two = 0;
	}
	if(traverse_find(suspended_ready_head, name) == NULL){
		in_three = 0;
	}
	if(traverse_find(suspended_blocked_head, name) == NULL){
		in_four = 0;
	}
	if(in_one == 0 && in_two == 0 && in_three == 0 && in_four == 0){
		return 0;
	}
	else{
		return 1;
	}
}

//Parameters: 	none
//Returns: void
//Functions called:	check_extra (determines if there are too many arguments)
//					show_all_ready (traverse and print suspended-ready queue)
//					show_all_suspended_ready (traverse and print suspended-ready queue)
//					show_all_blocked (traverse and print blocked queue)
//					show_all_suspended_blocked (traverse and print suspended-blocked queue)
//Prints display information for all PCBs in all queues
void show_all_pcbs(){
	int in_buff_size = 80;
	char buff_response[80];
	
	//traverse and print ready queue
	show_all_ready();
	printf("\n");
	printf("Press enter to see Suspended Ready!\n");
	sys_req(READ, TERMINAL, buff_response, &in_buff_size);
	printf("\n");
	
	
	//traverse and print the suspended ready queue
	show_all_suspended_ready();
	printf("\n");
	printf("Press enter to see Blocked!\n");
	sys_req(READ, TERMINAL, buff_response, &in_buff_size);
	printf("\n");
	
	//traverse and print blocked queue
	show_all_blocked();
	printf("\n");
	printf("Press enter to see Suspended Blocked!\n");
	sys_req(READ, TERMINAL, buff_response, &in_buff_size);
	printf("\n");
	
	//traverse and print the suspended blocked queue
	show_all_suspended_blocked();
}

//Parameters: 	none
//Returns: void
//Functions called:	show_pcb (print PCB with given name)
//Prints display information for all PCBs in ready queue
void show_all_ready(){
	pcb *curr = ready_head;
	int count = 0;
	int in_buff_size = 80;
	char buff_response[80];
	printf("**********START READY QUEUE*************\n");
	if(curr == NULL){ //No PCBs
		printf("No pcbs in ready queue!\n");
	}
	else{ //multiple PCBs
		while (curr != NULL) {
			if(count < 9){
				show_pcb(curr->name, "\0");
				count = count + 1;
			}
			if(count == 9){
				printf("End of page, press enter to continue!\n");
				sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				//continue printing
				if(*buff_response == '\n'){
					buff_response[0] = '\0';
					count = 0;
					curr = curr->next;
					printf("\n");
					show_pcb(curr->name, "\0");					
				}
				else{
					printf("You didn't press enter. You really should have pressed enter. >:( \n");
					sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				}
			}
			if(count != 9){//to prevent increase when enter is not pressed
				curr = curr->next;
			}
		}
	}
	printf("**********END READY QUEUE*************\n");
}

//Parameters: 	none
//Returns: void
//Functions called:	show_pcb (print PCB with given name)
//Prints display information for all PCBs in blocked queue
void show_all_blocked(){
	pcb *curr = blocked_head;
	int count = 0;
	int in_buff_size = 80;
	char buff_response[80];
	printf("**********START BLOCKED QUEUE*************\n");
	if(curr == NULL){ //No PCBs
		printf("No pcbs in blocked queue!\n");
	}
	else{ //multiple PCBs
		while (curr != NULL) {
			if(count < 9){
				show_pcb(curr->name, "\0");
				count = count + 1;
			}
			if(count == 9){
				printf("End of page, press enter to continue!\n");
				sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				//continue printing
				if(*buff_response == '\n'){
					buff_response[0] = '\0';
					count = 0;
					curr = curr->next;
					printf("\n");
					show_pcb(curr->name, "\0");					
				}
				else{
					printf("You didn't press enter. You really should have pressed enter. >:( \n");
					sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				}
			}
			if(count != 9){//to prevent increase when enter is not pressed
				curr = curr->next;
			}
		}
	}
	printf("**********END BLOCKED QUEUE*************\n");
}

//Parameters: 	none
//Returns: void
//Functions called:	show_pcb (print PCB with given name)
//Prints display information for all PCBs in suspended-ready queue
void show_all_suspended_ready(){
	pcb *curr = suspended_ready_head;
	int count = 0;
	int in_buff_size = 80;
	char buff_response[80];
	printf("**********START SUSPENDED READY QUEUE*************\n");
	if(curr == NULL){ //No PCBs
		printf("No pcbs in suspended ready queue!\n");
	}
	else{ //multiple PCBs
		while (curr != NULL) {
			if(count < 9){
				show_pcb(curr->name, "\0");
				count = count + 1;
			}
			if(count == 9){
				printf("End of page, press enter to continue!\n");
				sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				//continue printing
				if(*buff_response == '\n'){
					buff_response[0] = '\0';
					count = 0;
					curr = curr->next;
					printf("\n");
					show_pcb(curr->name, "\0");					
				}
				else{
					printf("You didn't press enter. You really should have pressed enter. >:( \n");
					sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				}
			}
			if(count != 9){//to prevent increase when enter is not pressed
				curr = curr->next;
			}
		}
	}
	printf("**********END SUSPENDED READY QUEUE*************\n");
}

//Parameters: 	none
//Returns: void
//Functions called:	show_pcb (print PCB with given name)
//Prints display information for all PCBs in suspended-blocked queue
void show_all_suspended_blocked(){
	pcb *curr = suspended_blocked_head;
	int count = 0;
	int in_buff_size = 80;
	char buff_response[80];
	printf("**********START SUSPENDED BLOCKED QUEUE*************\n");
	if(curr == NULL){ //No PCBs
		printf("No pcbs in suspended blocked queue!\n");
	}
	else{ //multiple PCBs
		while (curr != NULL) {
			if(count < 9){
				show_pcb(curr->name, "\0");
				count = count + 1;
			}
			if(count == 9){
				printf("End of page, press enter to continue!\n");
				sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				//continue printing
				if(*buff_response == '\n'){
					buff_response[0] = '\0';
					count = 0;
					curr = curr->next;
					printf("\n");
					show_pcb(curr->name, "\0");					
				}
				else{
					printf("You didn't press enter. You really should have pressed enter. >:( \n");
					sys_req(READ, TERMINAL, buff_response, &in_buff_size);
				}
			}
			if(count != 9){//to prevent increase when enter is not pressed
				curr = curr->next;
			}
		}
	}
	printf("**********END SUSPENDED BLOCKED QUEUE*************\n");
}


//Parameters: none
//Returns: nothing
//Functions called: delete_pcb (dequeues PCB and frees dynamically-allocated memory)
//Handles any last state or memory manipulation before MPX exits

//Fixed this, it was setting del to curr->prev after curr was NULL, so nothing was being deleted
void terminate_cleanup() {
	pcb *curr = ready_head;
	pcb *del = NULL;
	
	while(curr != NULL){
		del = curr;
		curr = curr->next;
		delete_pcb(del->name, "\0");
	}
	if(curr != NULL){
		delete_pcb(curr->name, "\0");
	}
	
	curr = blocked_head;
	del = NULL;
	while(curr != NULL){
		del = curr;
		curr = curr->next;
		delete_pcb(del->name, "\0");
	}
	if(curr != NULL){
		delete_pcb(curr->name, "\0");
	}
	
	curr = suspended_ready_head;
	del = NULL;
	while(curr != NULL){
		del = curr;
		curr = curr->next;
		delete_pcb(del->name, "\0");
	}
	if(curr != NULL){
		delete_pcb(curr->name, "\0");
	}
	
	curr = suspended_blocked_head;
	del = NULL;
	while(curr != NULL){
		del = curr;
		curr = curr->next;
		delete_pcb(del->name, "\0");
	}
	if(curr != NULL){
		delete_pcb(curr->name, "\0");
	}
}

pcb *get_ready_head(){
	return ready_head;
}
